package com.solt.prism.core;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import net.xeoh.plugins.base.PluginManager;
import net.xeoh.plugins.base.impl.PluginManagerFactory;
import net.xeoh.plugins.base.util.uri.ClassURI;

import org.apache.log4j.xml.DOMConfigurator;

import com.solt.prism.core.conf.Configurable;
import com.solt.prism.core.conf.Configuration;
import com.solt.prism.core.conf.PrismConfiguration;
import com.solt.prism.core.conf.news.NewsConf;
import com.solt.prism.core.utils.NewsPersistencer;
import com.solt.prism.core.utils.NewsPersistencerImpl;
import com.solt.prism.core.utils.PrismConstants;
import com.solt.prism.core.wrapper.XPathWrapper;
import com.solt.prism.plugins.NewsCrawler;
import com.solt.prism.plugins.NewsWrapper;
import com.solt.prism.plugins.PluginException;

public class MainExecutor {
	private static final int DEFAULT_PORT = 8080;
	private ExecutorService executor;
	private List<Worker> workers;
	private Configuration conf;
	private NewsPersistencer persistencer;
	private SocketServer server;

	public MainExecutor() throws IOException {
		executor = Executors.newCachedThreadPool();
		conf = PrismConfiguration.getInstance();
		persistencer = new NewsPersistencerImpl();
		workers = new ArrayList<Worker>();
		loadPlugins();
		int port = conf.getInt(PrismConstants.CRAWL_LISTEN_PORT, DEFAULT_PORT);
		server = new SocketServer(this, port);
		server.execute();
	}

	private void loadPlugins() {
		File dir = new File(PrismConstants.PLUGIN_HOME_DIR);
		Set<String> whileList = new HashSet<String>(conf.getStringCollection(PrismConstants.CRAWL_PLUGIN_WHITELIST));
		Set<String> blackList =  new HashSet<String>(conf.getStringCollection(PrismConstants.CRAWL_PLUGIN_BLACKLIST));
		boolean allowAll = false;
		if (blackList.contains("*")) {
			return;
		} else if (whileList.contains("*")) {
			allowAll = true;
		}
		File[] plugins = dir.listFiles();
		for (File plugin : plugins) {
			try {
				if ((allowAll || whileList.contains(plugin.getName())) && !blackList.contains(plugin.getName()))
					loadPlugin(plugin);
			} catch (PluginException e) {
				e.printStackTrace();
			}
		}
	}

	private void loadPlugin(File pluginDir) throws PluginException {
		try {
			NewsConf newsconf = loadNewsConf(pluginDir);
			if (newsconf == null) {
				return;
			}
			Configuration conf = loadConf(pluginDir);
			PluginManager pluginManager = PluginManagerFactory
					.createPluginManager();
			File[] list = pluginDir.listFiles(new FilenameFilter() {
				@Override
				public boolean accept(File dir, String name) {
					return dir.isFile() && name.endsWith(".jar");
				}
			});
			for (File file : list) {
				pluginManager.addPluginsFrom(file.toURI());
			}
			String backend = newsconf.getBackend();
			if (backend == null || backend.trim().isEmpty()) {
				backend = SimpleRssCrawler.class.getName();
			}
			pluginManager.addPluginsFrom(URI.create("classpath://" + backend));
			pluginManager.addPluginsFrom(ClassURI.PLUGIN(XPathWrapper.class));
			NewsWrapper wrapper = pluginManager.getPlugin(NewsWrapper.class);
			if (wrapper instanceof Configurable) {
				((Configurable) wrapper).setConf(conf);
			}

			NewsCrawler crawler = pluginManager.getPlugin(NewsCrawler.class);
			if (crawler instanceof Configurable) {
				((Configurable) crawler).setConf(conf);
			}
			pluginManager.shutdown();

			wrapper.init(newsconf.getNewsfields().getDetailSite().getField());
			crawler.init(newsconf, wrapper, persistencer);
			workers.add(new Worker(crawler));
		} catch (Exception e) {
		}
	}

	private Configuration loadConf(File pluginDir) {
		Configuration conf = new Configuration();
		File pluginConf = new File(pluginDir, PrismConstants.PLUGIN_CONFIG_FILE);
		if (pluginConf.isFile()) {
			try {
				conf.addResource(pluginConf.toURI().toURL());
			} catch (MalformedURLException e) {
				e.printStackTrace();
			}
		}
		conf.set(PrismConstants.PLUGIN_DIR, pluginDir.getAbsolutePath());
		conf.set(PrismConstants.PLUGIN_ID, pluginDir.getName());
		return conf;
	}

	private NewsConf loadNewsConf(File pluginDir) {
		JAXBContext jc;
		try {
			jc = JAXBContext.newInstance("com.solt.prism.core.conf.news");
			Unmarshaller unmarshaller = jc.createUnmarshaller();
			File newsConf = new File(pluginDir, PrismConstants.NEWS_CONFIG_FILE);
			if (newsConf.isFile()) {
				return (NewsConf) unmarshaller.unmarshal(newsConf);
			}
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return null;
	}

	public void execute() throws InterruptedException {
		for (Worker worker : workers) {
			executor.execute(worker);
		}
		executor.shutdown();
		while (!executor.awaitTermination(1000, TimeUnit.MINUTES)) {

		}
		persistencer.close();
	}
	
	public List<Worker> getWorkers() {
		return workers;
	}
	
	public void shutdown() {
		executor.shutdownNow();
	}

	public static void main(String[] args) throws InterruptedException, IOException {
		DOMConfigurator.configure("conf/log4j.xml");
		MainExecutor executor = new MainExecutor();
		executor.execute();
	}
}